#include "gdbmi.h"

#include <QDebug>
#include <QMetaType>

#ifdef Q_OS_LINUX
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <signal.h>
    #include<errno.h>
#endif

#define PROMPT_DEBUG_START    "------------------------- gdb started -------------------------"
#define PROMPT_DEBUG_EXIT    "------------------------- gdb exited --------------------------"
#define PROMPT_DEBUG_FAILED    "Failed to start gdb"

GDBMIClient::GDBMIClient (GDBMI * pThread) : m_pGDBMI (pThread)
{
    pThread->registerClient (this);
}

int GDBMIClient::sendRequest (const QString & request)
{
    return m_pGDBMI->sendRequest (request, this);
}

GDBMIAsyncClient::GDBMIAsyncClient (GDBMI * pThread) : GDBMIClient (pThread)
{
    pThread->setAsyncClient (this);
}

GDBMI::GDBMI (const QString & gdbName) 
    : m_gdbName (gdbName), m_debugger (0), m_asyncClient (0)
{
    m_token        = 1;
    m_started    = false;

    qRegisterMetaType<enum QProcess::ExitStatus> ("QProcess::ExitStatus");

    connectSignals ();
}

GDBMI::~GDBMI ()
{
    disconnectSignals ();
    if (m_debugger.state () != QProcess::NotRunning)
    {
        //// kill and wait until the process actually terminates
        m_debugger.kill ();
        m_debugger.waitForFinished (GDB_WAITTIMEOUT);
    }
}

void GDBMI::connectSignals ()
{
    connect (& m_debugger, SIGNAL (readyReadStandardOutput ()),
        this, SLOT (slotStandardOutput ()));
    connect (& m_debugger, SIGNAL (readyReadStandardError ()),
        this, SLOT (slotStandardError ()));
    connect (& m_debugger, SIGNAL (finished (int, QProcess::ExitStatus)),
        this, SLOT (slotFinish (int, QProcess::ExitStatus)));
}

void GDBMI::disconnectSignals ()
{
    disconnect (& m_debugger, SIGNAL (readyReadStandardOutput ()),
        this, SLOT (slotStandardOutput ()));
    disconnect (& m_debugger, SIGNAL (readyReadStandardError ()),
        this, SLOT (slotStandardError ()));
    disconnect (& m_debugger, SIGNAL (finished (int, QProcess::ExitStatus)),
        this, SLOT (slotFinish (int, QProcess::ExitStatus)));
}

QStringList GDBMI::breakIntoOutputs (QString & output)
{
    int pos, last;
    QStringList outputList;
    QRegExp separator ("\\(gdb\\)[ ]*(\\r)?\\n");

    /*
        The gdb /mi output will consists of formatted text, and end with
        "(gdb)[ ]*(\r)?\n". The only exception is "^exit(\r)?\n", which
        is emitted when gdb exit due to "-gdb-exit" command.
    */
    if (! m_buffer.isEmpty ())
    {
        output.insert (0, m_buffer);
        m_buffer.clear ();
    }

    last = 0;
    while ((pos = separator.indexIn (output, last)) != -1)
    {
        pos += separator.matchedLength ();

        outputList.push_back (output.mid (last, pos - last));
        last = pos;
    }

    if (last < output.length ())
    {
        // here we have some output text which do NOT
        // end with "(gdb)[ ]*(\r)?\n"
        m_buffer = output.mid (last);

        QRegExp exitOutput ("\\^exit[ ]*(\\r)?\\n");
        if (exitOutput.exactMatch (m_buffer))
            m_buffer.clear ();
    }

    return outputList;
}

bool GDBMI::startDebug (const QString & target)
{
    if (m_debugger.state () != QProcess::NotRunning) {
        m_debugger.kill ();
    }

    m_target = target.trimmed ();
    m_token = 1;
    m_buffer.clear ();
    m_requests.clear ();

#ifdef Q_OS_WIN
    m_target.replace ('/', '\\');
#endif

    if (launchDebugger ())
    {
        emitMessage (PROMPT_DEBUG_START);

        m_started = true;
        foreach (GDBMIClient * client, m_syncClients)
            client->onStarted ();

        return true;
    }
    else
    {
        emitMessage (PROMPT_DEBUG_FAILED, CRITICAL);

        return false;
    }
}

void GDBMI::stopDebug ()
{
    if (m_started)
    {
            /*
        m_debugger.kill ();
        m_debugger.waitForFinished (WAIT_TIMEOUT);*/
            m_debugger.kill ();
            /*
            int pid =  m_debugger.pid();
        if (!kill(pid,SIGKILL)) {
                    //m_debugger.waitForFinished (WAIT_TIMEOUT);
            }  */
        }
}

int GDBMI::sendRequest (
                const QString & request, GDBMIClient * client)
{
    if (! m_started)
    {
        qDebug ("Send request \"%s\" while gdb is not running.",
            request.toLocal8Bit ().constData ());
        return 0;
    }

#ifdef Q_OS_LINUX
    if (QString::compare(request,"Contol-C") == 0) {
        if (!kill(((pid_t)m_debugger.pid()),SIGINT)) {
                    qDebug("%d\tSIGINT",errno);
        }
         return 0;
    }
#endif

    int token = m_token ++;

    // notice that NO blank space exist between token and
    // mi command, otherwise the command will NOT be
    // recogonized (GNU gdb 6.8)
    QString cmd = QString ("%1%2\r\n").arg (token).arg (request);
    QByteArray cmdBytes = cmd.toLocal8Bit ();

    qDebug ("Send request: %s\n", cmdBytes.constData ());
    if (m_debugger.write (cmdBytes) != -1)
    {
        // client will handle it asynchronously
        m_requests.push_back (RequestPair (token, client));
        return token;
    }
    else
    {
        qDebug ("Failed to send request %s", cmdBytes.constData ());
        return 0;
    }
}

bool GDBMI::launchDebugger ()
{
    m_debugger.setWorkingDirectory (m_workDirectory);

    emitMessage ("launch debugger...\n");
    qDebug("gdb start with target ");
    qDebug(m_target.toUtf8().constData());
    m_debugger.start (m_gdbName,
        QStringList() << "--interpreter=mi" << m_target);

    if (! m_debugger.waitForStarted (GDB_WAITTIMEOUT))
        return false;
    else
        return true;
}

void GDBMI::slotStandardOutput ()
{
    if (! m_started)
        return;

    //FILE *testfile = fopen("e:\\output.txt","w");
    QString text = m_debugger.readAllStandardOutput ();
    qDebug("[%s]",text.toUtf8().constData());
    //fprintf(testfile,"%s",text.toLatin1().constData());
    if (! text.isEmpty ())
    {
        QStringList stringList = breakIntoOutputs (text);

        foreach (QString stringOutput, stringList) {
            qDebug("=========================");
            qDebug(stringOutput.toUtf8().constData());

        }

        foreach (QString stringOutput, stringList)
        {
            //qDebug ("process {%s}\n", stringOutput.toLatin1 ().constData ());
            //qDebug("process{%s}\n",stringOutput.left(100).toLatin1().constData());
            //qDebug("process{%s}\n",stringOutput.right(50).toLatin1().constData());
            //qDebug("--{%d}\n",stringOutput.length());
            //FILE * fp = fopen("changelist.txt","a");
            //fprintf(fp,"%s\n",stringOutput.toLatin1().constData());

            QStringList tmpList = stringOutput.split('\n');
            QString tmpBuffer;
            int xflag = 0;
            foreach(QString tmpStr,tmpList) {
                //qDebug("[%s]",tmpStr.toLatin1().constData());
                if (tmpStr[0] == '=') continue;
                if (tmpStr.startsWith("~\"GNU gdb")) {
                    qDebug(tmpStr.toLatin1().constData());
                    int start;
                    int end = tmpStr.indexOf("\\n");
                    start = tmpStr.indexOf("(GDB)");
                    if (start == -1) {
                        start = tmpStr.indexOf("gdb");
                        start += 4;
                    } else {
                        start += 6;
                    }
                    QString t = tmpStr.mid(start, end - start);
                    //use the old version GDB/MI command to get memmory infos, ensure non-error on 64-bits machines
                    //
                    //if (t == "7.3" || t == "7.3.1" || t == "7.4" || t == "7.5") {
                    //    m_verIsNew = true;
                    //}
                    //else {
                        m_verIsNew = false;
                    //}
                    qDebug(t.toUtf8().constData());
                }
                if ((tmpStr.indexOf('*') != -1 || tmpStr.indexOf('^') !=-1)) {
                    xflag = 1;
                }
                if (xflag && (tmpStr[0] == '*') && tmpBuffer.length() > 0) {
                    continue;
                }
                tmpBuffer = tmpBuffer + tmpStr.trimmed() + '\n' ;
            }
            stringOutput = tmpBuffer.trimmed() + '\n';


            //qDebug("{{%s}}",stringOutput.toLatin1().constData());
            //fprintf(testfile,"%s",stringOutput.toLatin1().constData());
            //fclose(testfile);
            //qDebug("=========================");*/
            /*
                when the send -var-update --simplie-values * to gdb,gdb may send back a string such as "10^done,changelist=[{},...,{}] (gdb)"
                .But this string may be too long to Parse,so the next part of code is to solve this problem.
                I have try when this changlist string less than 308342 ,parse will work well.I decide I'll divide the string when is longer
                than 300,100 to avoid guide crash.
                START HERE
            */

            //if (stringOutput.length() > 300100) {
            //    int token_record = 0;
            //    GDBMIClient * client = 0;
            //    int m_start,m_end;
            //    m_start = stringOutput.indexOf('[');
            //    m_end = stringOutput.lastIndexOf(']');
            //    QString start_str,end_str,mid_str;
            //    start_str = stringOutput.left(m_start+1);
            //    end_str = stringOutput.right(stringOutput.length() - m_end);
            //    stringOutput = stringOutput.mid(m_start+1,m_end-m_start-1);
            //    /*qDebug(start_str.toLatin1().constData());
            //    qDebug(end_str.toLatin1().constData());
            //    qDebug(stringOutput.left(20).toLatin1().constData());
            //    qDebug(stringOutput.right(20).toLatin1().constData());*/
            //
            //    while (stringOutput.length() > 0) {
            //        if (stringOutput.length() > 300000) {
            //            mid_str = stringOutput.left(300000);
            //            m_start = mid_str.lastIndexOf("\"},{name=\"") + 2;
            //            mid_str = stringOutput.left(m_start);
            //            stringOutput = stringOutput.right(stringOutput.length() - m_start - 1);
            //        } else {
            //            mid_str = stringOutput;stringOutput = "";
            //        }

            //        /*qDebug(mid_str.left(20).toLatin1().constData());
            //        qDebug(mid_str.right(20).toLatin1().constData());
            //        qDebug(stringOutput.left(20).toLatin1().constData());*/

            //        mid_str = start_str + mid_str + end_str;
            //        /*qDebug(mid_str.left(50).toLatin1().constData());
            //        qDebug(mid_str.right(50).toLatin1().constData());
            //        qDebug("mid_str:%d",mid_str.length());*/
            //        MIGDBOutput * output = ParseMIOutput (mid_str.toLatin1 ());
            //        //qDebug("mid_str:%d",mid_str.length());


            //        if (output->m_pResultRecord)
            //        {
            //            // dispatch this record to appropriate client
            //            // according to the token associated with this
            //            // record.
            //
            //            token_record = output->m_pResultRecord->m_token;
            //            if (token_record)
            //            {
            //                if (client == 0) {
            //                    client = getClientByToken (token_record);
            //                }
            //                if (client)
            //                {
            //                    client->onResultRecord (output->m_pResultRecord);
            //                }
            //            }
            //        }
            //        if (output)
            //            ReleaseMIOutput (output);
            //    }
            //    return ;
            //}

            /*
                END HERE
            */



            MIGDBOutput * output = ParseMIOutput (stringOutput.toLocal8Bit ());
            //fclose(fp);

            if (!output)
            {
                // sth. unexpected
                //qDebug("Failed(%s)",stringOutput.toLatin1().constData());
                int token = stringOutput.left(stringOutput.indexOf('^')).toInt();
                //qDebug ("token %d\n",token);
                if (token) {
                    GDBMIClient * client = getClientByToken (token);
                    if (client) {
                        client->onResultParseErr(token);
                    }
                }
                return;
            }

            if (output->m_pResultRecord)
            {
                // dispatch this record to appropriate client
                // according to the token associated with this
                // record.
                bool done = false;
                int token = output->m_pResultRecord->m_token;

                static int last_token =0;
                if (output->m_pResultRecord->m_class == RC_RUNNING) {
                    last_token = token;
                } else {
                    last_token = 0;
                }

                //qDebug ("token %d last_token %d\n",token,last_token);


                if (token)
                {
                    GDBMIClient * client = getClientByToken (token,1);
                    //qDebug("token %d",token);
                    if (client)
                    {
                        client->onResultRecord (output->m_pResultRecord);
                        done = true;
                    }
                }
                else if (last_token) {
                    //qDebug("last_token %d",last_token);
                    GDBMIClient * client = getClientByToken (last_token);
                    if (client)
                    {
                        client->onResultRecord (output->m_pResultRecord);
                        done = true;
                    }
                }

                if (! done && m_asyncClient)
                {
                    // no token associated with it or no client expect this
                    // token.
                    m_asyncClient->onResultRecord (output->m_pResultRecord);
                }
            }

            if (output->m_pOutOfBandRecord && m_asyncClient)
            {
                MIOutOfBandRecord * record = output->m_pOutOfBandRecord;
                while (record)
                {
                    if (record->m_type == ASYNC)
                    {
                        if (record->m_pAsyncRecord)
                            m_asyncClient->onAsyncRecord (record->m_pAsyncRecord);
                    }
                    else // STREAM
                    {
                        if (record->m_pStreamRecord)
                            m_asyncClient->onStreamRecord (record->m_pStreamRecord);
                    }

                    record = record->m_pNext;
                }
            }

            ReleaseMIOutput (output);
        }
    }
}

void GDBMI::slotStandardError ()
{
    //qDebug("slotStandardError");
    if (! m_started)
        return;

    QString text = m_debugger.readAllStandardError ();
    emitMessage (text, CRITICAL);
}

void GDBMI::slotFinish (int /*exitCode*/, QProcess::ExitStatus exitStatus)
{
    emitMessage (PROMPT_DEBUG_EXIT);

    m_started = false;

    // broadcast to all clients

    for (std::list<GDBMIClient *>::iterator it = m_syncClients.begin ();
        it != m_syncClients.end ();
        ++ it)
        (* it)->onExit ();

    if (exitStatus == QProcess::CrashExit)
    {
        // sth. unexpected occured

    }
}

bool GDBMI::verIsNew ()
{
    return m_verIsNew;
}
